<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // 参数默认值可以与解构赋值的默认值，结合起来使用
    function foo ({x, y = 5}) {
      console.log(x, y)
    }
    foo({})// undefined 5
    foo({x:1}) // 1 5
    foo({x:1,y:2}) // 1 2
    foo()// Cannot read property 'x' of undefined
    /*
    上面的代码只使用了对象的解构赋值默认值，没有使用函数参数的默认值。
    只有当函数foo的参数是一个对象时，变量x和y才会通过解构赋值生成.如果foo调用时没有提供参数，变量x和y就不会生成，从而报错
    提供函数参数的默认值就可以避免这种情况
    */
    function foo1 ({x, y = 5} = {}) { //如果没有提供参数，函数foo1的参数默认为一个空对象
      console.log(x, y)
    }
    foo1() // 此时不会报错 undefined 5
  </script>
  <script>
    function fetch(url, { body = '', method = 'GET', headers = {} }) {
      console.log(method)
    }
    fetch('http://example.com', {}) // 'GET'
    // fetch('http://example.com') // 报错
    /*
    上面的代码中，如果函数fetch的第二个参数是一个对象，就可以为它的三个属性设置默认值。这种写法不能省略第二个参数
    */

    function fetch1(url, { body = '', method = 'GET', headers = {} } = {}) {// 为第二个参数传默认值，此时可以省略第二个参数
      console.log(method)
    }
    fetch1('http://example.com') // 不报错 'GET'
    /*
    上面代码中，函数fetch1没有第二个参数时，函数参数的默认值就会生效，然后才是解构赋值的默认值生效，变量method才会取到默认值GET
    */
  </script>
  <script>
    // 作为练习，请问下面俩种写法有什么差别
    // 写法一
    function m1({x = 0, y = 0} = {}) {
      return [x, y]
    }
    // 写法二
    function m2({x, y} = {x: 0, y: 0}) {
      return [x, y]
    }
    // 函数没有参数的情况
    m1() // [0, 0]
    m2() // [0, 0]

    // x 和 y 都有值的情况
    m1({x: 3, y: 8}) // [3, 8]
    m2({x: 3, y: 8}) // [3, 8]

    // x 有值，y 无值的情况
    m1({x: 3}) // [3, 0] {x =0, y = 0} = {x: 3}
    m2({x: 3}) // [3, undefined]

    // x 和 y 都无值的情况
    m1({}) // [0, 0];
    m2({}) // [undefined, undefined]

    m1({z: 3}) // [0, 0]
    m2({z: 3}) // [undefined, undefined]
    /*
    上面俩种写法偶读对函数的参数设定了默认值，区别是写法一函数参数的默认值是空对象，但是设置了对象解构赋值的默认值；
    写法二函数参数的默认值是一个有具体属性的对象。但是没有设置对象解构赋值的默认值。
    */
  </script>
  <script>
    //参数默认值的位置
    /*
    通常情况下，定义了默认值的参数，应该是函数的尾参数。因为这样比较容易看出来，到底省略了哪些参数。如果非尾部的参数设置默认值，
    实际上这个参数是没法省略的。
    */
   function f(x=1, y) {
     return [x,y]
   }
   f() // [1, undefined]
   f(2)// [2, undefined]
   f(undefined, 1) // [1,1]
   function fn(x, y=5, z) {
    return [x,y,z]
   }
   fn()//[undefined, 5, undefined]
   fn(1) // [1, 5, undefined]
   fn(1, undefined, 2) [1, 5, 2]
   /*
   上面的代码中，有默认值的参数都不是尾参数，这时，无法只省略该参数，而不省略它后面的参数，除非显示的输入undefined，null没有这个效果。
   */
  </script>
  <script>
   // 函数的length属性
   // 指定了默认值以后，函数的length属性，将返回没有指定默认值的参数个数。也就是说，指定默认值后，length属性将失真。
   (function(a) {}).length // 1
   (function(a = 5) {}).length // 0
   (function(a, b, c = 5) {}).length //2
   /*
   上面代码中，length属性的返回值，等于函数的参数个数减去指定了默认值的参数个数。
   这是因为length属性的含义是:该函数预期传入的参数个数。某个参数指定默认值以后，预期传入的参数个数就不包括这个参数了。
   同理，后文的rest参数也不会计入length属性。
   */
  (function(...args) {}).length // 0
  // 如果设置了默认值的参数不是尾参数，那么length属性也不再计入后面的参数
  (function(a = 0, b, c) {}).length // 0
  (function(a, b = 1, c) {}),length // 1
  </script>
  <script>
    // 作用域
    /*
    一旦设置了参数的默认值，函数进行声明初始化时，参数会形成一个单独的作用域(context)。
    等到初始化结束，这个作用域就会消失。这种语法行为，在不设置参数默认值时，是不会出现的。
    */
   var x = 1
   function f(x, y = x) { // 参数y的默认值等于变量x,调用函数f时，参数形成一个单独的作用域。
    // 在这个作用域里面，默认值变量x指向第一个参数x，而不是全局变量x,所有输出是2
     console.log(y)
   }
   f(2) // 2
  </script>
  <script>
    var x = 1
    function f(y = x) { // 在f调用时，参数y = x形成一个单独的作用域。这个作用域里面，变量x本身没有定义，所以指向外层的全局变量x
      // 函数调用时，函数体内部的局部变量x影响不到默认值变量x,如果此时全局变量x不存在，就会报错
      var x = 2
      console.log(y) // 1
    }
    f()
    // 如果写成 function f(x = x) {} 也会报错，参数x = x形成一个单独作用域，实际执行的是 let x = x,由于暂时性死区的原因，这行代码会报错。
  </script>
  <script>
    // 如果参数的默认值是一个函数，该函数也遵守这个规则
    var foo = 'outer'
    function bar(func = () => foo) {
      var foo = 'inner'
      console.log(func())
    }
    bar() // // outer
    /**
     * 上面代码中，函数bar的参数func的默认值是一个匿名函数，返回值为变量foo，函数参数形成单独作用域
     * 在这个单独的作用域里面，并没有定义变量foo，所以 foo指向外层全局变量，因此输出 'outer'
    */
  </script>
  <script>
    var x = 1
    function foo(x,y = function() {x = 2}) {
      var x = 3
      y()
      console.log(x)
    }
    foo() // 3
    console.log(x) // 1
    /**
     * 上面代码中，参数 y = function () {x = 2} 形成单独作用域，在这个作用域里面
     * 首先声明了变量x，然后声明了变量y, y的默认值是一个匿名函数。这个匿名函数内部的变量x，指向了同一个作用域的第一个参数x
     * 函数foo内部又声明了一个内部变量x, 该变量与第一个参数x由于不是同一个作用域，所以不是同一个变量，
     * 因此执行y()后，内部变量和外部全局变量x的值都没变。
     * y()执行时，会把 x 赋值为 2 ，这里的x是参数作用域中变量，与全局变量和局部变量都不是一个作用域，变量也不是同一个变量！！！！
     *
    */
  </script>
  <script>
    // 如果将var x = 3 的var去除，函数foo的内部变量x就指向第一个参数x，与匿名函数内部的x是一致的，所以，最后输出的是2，而外层的全局变量x
    // 依然不受影响
    var x = 1
    function foo(x,y = function() {x = 2}) {
      x = 3
      y()
      console.log(x)
    }
    foo() // 2
    console.log(x) // 1
  </script>
  <script>
    // 应用
    // 利用参数默认值，可以指定某一个参数不得省略，如果省略就抛出一个错误。
    function throwIfMissing() {
      throw new Error('Missing paramter')
    }
    function foo (mustBeProvided = throwIfMissing()) {
      return mustBeProvided
    }
    foo() // Error: Missing parameter
    /**
     * 上面代码的foo函数，如果调用时没有传参数，就会调用throwIfMissing函数，从而抛出一个错误。
     * 从上面的代码还可以看到，参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(注意函数名后还有一对圆括号)
     * 这表明参数的默认值不是在定义时执行的，而是在运行时执行。如果参数已经赋值，默认值中的函数就不会运行，
     * 另外，可以将参数默认值设为undefined，表明这个参数是可以省略的。
    */
   function fn(optional = undefined) {}
  </script>
</body>
</html>